#include <filesystem>
#include <Windows.h>

#include <reframework/API.hpp>
#include <sol/sol.hpp>

using API = reframework::API;
namespace fs = std::filesystem;

struct Plugin {
    lua_State* lua{};
};

Plugin* g_plugin{};

BOOL APIENTRY DllMain(HMODULE, DWORD reason, LPVOID) {
    if (reason == DLL_PROCESS_ATTACH) {
        g_plugin = new Plugin{};
    }

    return TRUE;
}

const fs::path get_data_path() {
    std::string modpath{};
    modpath.resize(1024, 0);
    modpath.resize(GetModuleFileName(nullptr, modpath.data(), (DWORD)modpath.size()));

    return fs::path{ modpath }.parent_path() / "reframework" / "data";
}

void lua_check(sol::this_state l, const std::string& methodname, const std::string& filepath) {
    if (filepath.find("..") != std::string::npos) {
        lua_pushstring(l, (methodname + " does not allow access to parent directories").c_str());
        lua_error(l);
    }
    if (fs::path(filepath).is_absolute()) {
        lua_pushstring(l, (methodname + " does not allow the use of absolute paths").c_str());
        lua_error(l);
    }
}

template <typename TP>
std::time_t to_time_t(TP tp) {
    using namespace std::chrono;
    auto sctp = time_point_cast<system_clock::duration>(tp - TP::clock::now()
        + system_clock::now());
    return system_clock::to_time_t(sctp);
}

sol::table get_last_write_time(sol::this_state l, const std::string& filepath) try {
    sol::state_view lua{ l };
    auto ftime = last_write_time(get_data_path() / filepath);
    auto ctime = to_time_t(ftime);
    tm tm;
    auto ltime = localtime_s(&tm, &ctime);
    auto t = lua.create_table();
    t["year"] = 1900 + tm.tm_year;
    t["mon"] = tm.tm_mon + 1;
    t["day"] = tm.tm_mday;
    t["hour"] = tm.tm_hour;
    t["min"] = tm.tm_min;
    t["sec"] = tm.tm_sec;
    return t;
}
catch (const std::exception& e) {
    OutputDebugStringA(e.what());
    API::get()->log_error("[reframework-io-extend] [get_last_write_time] %s", e.what());
    return sol::nil;
}

void on_ref_lua_state_created(lua_State* l) try {
    g_plugin->lua = l;
    sol::state_view lua{ l };

    auto file = lua.create_table();
    auto dir = lua.create_table();

    file["delete"] = [](sol::this_state l, const std::string& filepath) {
        lua_check(l, "file.delete", filepath);
        return remove(get_data_path() / filepath);
    };
    file["rename"] = [](sol::this_state l, const std::string& srcFilepath, const std::string& destFilepath) {
        lua_check(l, "file.rename", srcFilepath);
        auto datapath = get_data_path();
        rename(datapath / srcFilepath, datapath / destFilepath);
    };
    file["copy"] = [](sol::this_state l, const std::string& srcFilepath, const std::string& destFilepath) {
        lua_check(l, "file.copy", srcFilepath);
        auto datapath = get_data_path();
        copy(datapath / srcFilepath, datapath / destFilepath);
    };
    file["exists"] = [](const std::string& filepath) {
        return exists(get_data_path() / filepath);
    };
    file["last_write_time"] = get_last_write_time;

    dir["exists"] = [](const std::string& path) {
        return exists(get_data_path() / path);
    };
    dir["get_files"] = [](sol::this_state l, const std::string& path, const std::string& ext, bool recursively) {
        sol::state_view lua{ l };
        auto result = lua.create_table();
        auto paths = get_data_path() / path;

        fs::create_directories(paths);

        auto i = 0;
        if (recursively) {
            for (auto const& entry : fs::recursive_directory_iterator{ paths }) {
                if (!entry.is_regular_file() && !entry.is_symlink()) {
                    continue;
                }
                if (ext == ".*" || entry.path().extension() == ext) 
                    result[++i] = relative(entry.path(), paths).string();
            }
        }
        else {
            for (auto const& entry : fs::directory_iterator{ paths }) {
                if (!entry.is_regular_file() && !entry.is_symlink()) {
                    continue;
                }
                if (ext == ".*" || entry.path().extension() == ext)
                    result[++i] = relative(entry.path(), paths).string();
            }
        }
        return result;
    };
    lua["_io_file"] = file;
    lua["_io_dir"] = dir;
} catch (const std::exception& e) {
    OutputDebugStringA(e.what());
    API::get()->log_error("[reframework-io-extend] [on_ref_lua_state_created] %s", e.what());
}

void on_ref_lua_state_destroyed(lua_State* l) try {
    g_plugin->lua = nullptr;
}
catch (const std::exception& e) {
    OutputDebugStringA(e.what());
    API::get()->log_error("[reframework-io-extend] [on_ref_lua_state_destroyed] %s", e.what());
}

extern "C" __declspec(dllexport) void reframework_plugin_required_version(REFrameworkPluginVersion * version) {
    version->major = REFRAMEWORK_PLUGIN_VERSION_MAJOR;
    version->minor = REFRAMEWORK_PLUGIN_VERSION_MINOR;
    version->patch = REFRAMEWORK_PLUGIN_VERSION_PATCH;
}

extern "C" __declspec(dllexport) bool reframework_plugin_initialize(const REFrameworkPluginInitializeParam * param) {
    reframework::API::initialize(param);
    param->functions->on_lua_state_created(on_ref_lua_state_created);
    param->functions->on_lua_state_destroyed(on_ref_lua_state_destroyed);
    return true;
}
